// 
// Copyright (c) 2006-2008 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.IO;
using System.Collections.Generic;

namespace Anculus.Core
{
	public abstract class AbstractConfigurationSection : IConfigurationSection
	{
		protected string _name;
		protected IConfigurationSection _parent;

		protected Dictionary<string, IConfigurationSection> _children;
		
		protected AbstractConfigurationSection ()
			: this (null, "root")
		{
		}

		protected AbstractConfigurationSection (IConfigurationSection parent, string name)
		{
			this._parent = parent;
			this._name = name;
			
			_children = new Dictionary<string, IConfigurationSection> ();
		}

		public string Name
		{
			get { return _name; }
		}

		public virtual IConfigurationSection this [string name]
		{
			get
			{
				if (name == null)
					throw new ArgumentNullException ("name");
				
				IConfigurationSection section = null;
				if (!_children.TryGetValue (name, out section)) {
					section = CreateChildSection (name);
					_children.Add (name, section);
					
				}
				return section;
			}
			protected internal set
			{
				if (name == null)
					throw new ArgumentNullException ("name");
				if (value == null)
					throw new ArgumentNullException ("value");
				
				if (_children.ContainsKey (name))
					_children[name] = value;
				else
					_children.Add (name, value);
			}
		}

		protected abstract IConfigurationSection CreateChildSection (string name);
		
		public virtual IConfigurationSection Parent
		{
			get { return _parent; }
		}

		public virtual bool IsRoot
		{
			get { return _parent == null; }
		}

		public virtual bool IsReadOnly
		{
			get { return false; }
		}

		public abstract bool ContainsKey (string name);
		
		public abstract IEnumerable<string> Keys { get; }
		
		public virtual bool ContainsSection (string name)
		{
			if (name == null)
				throw new ArgumentNullException ("name");

			return _children.ContainsKey (name);
		}

		public virtual IEnumerable<string> SectionNames
		{
			get {
				if (_children == null)
					throw new NotSupportedException ("No child sections are allowed");
				return _children.Keys;
			}
		}

		public virtual IEnumerable<IConfigurationSection> Sections
		{
			get {
				if (_children == null)
					throw new NotSupportedException ("No child sections are allowed");
				return _children.Values;
			}
		}
		
		public virtual void RemoveSection (string name)
		{
			if (name == null)
				throw new ArgumentNullException ("name");
			
			_children.Remove (name);
		}

		public abstract void Remove (string name);

		public T Get<T> (string name) where T : new ()
		{
			if (!IsSerializable (typeof (T))) {
				throw new NotSupportedException ("The generic type must be Serializable in order to be stored in the configuration.");
			} else {
				object value = GetInternal (name, ConfigurationObjectType.GenericObject);
				if (value != null)
					return (T)value;
				else
					return default(T);
			}
		}

		public object GetObject (string name)
		{
			return GetInternal (name, ConfigurationObjectType.Object);
		}
		
		public abstract ConfigurationObjectType GetObjectType (string name);

		public byte GetByte (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Byte);
			if (value != null)
				return (byte)value;
			else
				return byte.MinValue;
		}

		public sbyte GetSByte (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.SByte);
			if (value != null)
				return (sbyte)value;
			else
				return sbyte.MinValue;
		}

		public bool GetBool (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Bool);
			if (value != null)
				return (bool)value;
			else
				return false;
		}

		public short GetShort (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Short);
			if (value != null)
				return (short)value;
			else
				return (short)0;
		}

		public int GetInt (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Int);
			if (value != null)
				return (int)value;
			else
				return 0;
		}

		public long GetLong (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Long);
			if (value != null)
				return (long)value;
			else
				return 0L;
		}

		public ushort GetUShort (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.UShort);
			if (value != null)
				return (ushort)value;
			else
				return (ushort)0;
		}

		public uint GetUInt (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.UInt);
			if (value != null)
				return (uint)value;
			else
				return 0U;
		}

		public ulong GetULong (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.ULong);
			if (value != null)
				return (ulong)value;
			else
				return 0UL;
		}

		public float GetFloat (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Float);
			if (value != null)
				return (float)value;
			else
				return 0.0F;
		}

		public double GetDouble (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Double);
			if (value != null)
				return (double)value;
			else
				return 0.0;
		}

		public string GetString (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.String);
			if (value != null)
				return value.ToString ();
			else
				return null;
		}

		public char GetChar (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Char);
			if (value != null)
				return (char)value;
			else
				return char.MinValue;
		}

		public decimal GetDecimal (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.Decimal);
			if (value != null)
				return (decimal)value;
			else
				return 0.0M;
		}

		public DateTime GetDateTime (string name)
		{
			object value = GetInternal (name, ConfigurationObjectType.DateTime);
			if (value != null)
				return new DateTime ((long)value);
			else
				return DateTime.MinValue;
		}

		public T[] GetArray<T> (string name)
		{
			if (!IsSerializable (typeof (T))) {
				throw new NotSupportedException ("The generic type must be Serializable in order to be stored in the configuration.");
			} else {
				object value = GetInternal (name, ConfigurationObjectType.Array);
				if (value != null)
					return (T[])value;
				else
					return new T[0];
			}
		}

		public IList<T> GetList<T> (string name)
		{
			if (!IsSerializable (typeof (T))) {
				throw new NotSupportedException ("The generic type must be Serializable in order to be stored in the configuration.");
			} else {
				object value = GetInternal (name, ConfigurationObjectType.List);
				if (value != null)
					return (IList<T>)value;
				else
					return new List<T> ();
			}
		}

		public byte GetByte (string name, byte defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Byte) == 0)
				return GetByte (name);
			else
				return defaultValue;
		}

		public sbyte GetSByte (string name, sbyte defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.SByte) == 0)
				return GetSByte (name);
			else
				return defaultValue;
		}

		public bool GetBool (string name, bool defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Bool) == 0)
				return GetBool (name);
			else
				return defaultValue;
		}

		public short GetShort (string name, short defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Short) == 0)
				return GetShort (name);
			else
				return defaultValue;
		}

		public int GetInt (string name, int defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Int) == 0)
				return GetInt (name);
			else
				return defaultValue;
		}

		public long GetLong (string name, long defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Long) == 0)
				return GetLong (name);
			else
				return defaultValue;
		}

		public ushort GetUShort (string name, ushort defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.UShort) == 0)
				return GetUShort (name);
			else
				return defaultValue;
		}

		public uint GetUInt (string name, uint defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.UInt) == 0)
				return GetUInt (name);
			else
				return defaultValue;
		}

		public ulong GetULong (string name, ulong defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.ULong) == 0)
				return GetULong (name);
			else
				return defaultValue;
		}

		public float GetFloat (string name, float defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Float) == 0)
				return GetFloat (name);
			else
				return defaultValue;
		}

		public double GetDouble (string name, double defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Double) == 0)
				return GetDouble (name);
			else
				return defaultValue;
		}

		public string GetString (string name, string defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.String) == 0)
				return GetString (name);
			else
				return defaultValue;
		}

		public char GetChar (string name, char defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Char) == 0)
				return GetChar (name);
			else
				return defaultValue;
		}

		public decimal GetDecimal (string name, decimal defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Decimal) == 0)
				return GetDecimal (name);
			else
				return defaultValue;
		}

		public DateTime GetDateTime (string name, DateTime defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.DateTime) == 0)
				return GetDateTime (name);
			else
				return defaultValue;
		}

		public T[] GetArray<T> (string name, T[] defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.Array) == 0)
				return GetArray<T> (name);
			else
				return defaultValue;
		}

		public IList<T> GetList<T> (string name, IList<T> defaultValue)
		{
			if (CheckType (name, ConfigurationObjectType.List) == 0)
				return GetList<T> (name);
			else
				return defaultValue;
		}

		public void Set<T> (string name, T value)
		{
			if (value == null) {
				SetInternal (name, ConfigurationObjectType.Null, null);
			} else {
				Type type = typeof (T);
				if (!IsSerializable (type))
					throw new NotSupportedException ("The object must be Serializable in order to be stored in the configuration.");
				else
					SetInternal (name, ConfigurationObjectType.GenericObject, value);
			}
		}

		public void SetObject (string name, object value)
		{
			if (value == null) {
				SetInternal (name, ConfigurationObjectType.Null, null);
			} else {
				Type type = value.GetType ();
				if (!IsSerializable (type))
					throw new NotSupportedException ("The object must be Serializable in order to be stored in the configuration.");
				else
					SetInternal (name, ConfigurationObjectType.Object, value);
			}
		}

		public void SetByte (string name, byte value)
		{
			SetInternal (name, ConfigurationObjectType.Byte, value);
		}

		public void SetSByte (string name, sbyte value)
		{
			SetInternal (name, ConfigurationObjectType.SByte, value);
		}

		public void SetBool (string name, bool value)
		{
			SetInternal (name, ConfigurationObjectType.Bool, value);
		}

		public void SetShort (string name, short value)
		{
			SetInternal (name, ConfigurationObjectType.Short, value);
		}

		public void SetInt (string name, int value)
		{
			SetInternal (name, ConfigurationObjectType.Int, value);
		}

		public void SetLong (string name, long value)
		{
			SetInternal (name, ConfigurationObjectType.Long, value);
		}

		public void SetUShort (string name, ushort value)
		{
			SetInternal (name, ConfigurationObjectType.UShort, value);
		}

		public void SetUInt (string name, uint value)
		{
			SetInternal (name, ConfigurationObjectType.UInt, value);
		}

		public void SetULong (string name, ulong value)
		{
			SetInternal (name, ConfigurationObjectType.ULong, value);
		}

		public void SetFloat (string name, float value)
		{
			SetInternal (name, ConfigurationObjectType.Float, value);
		}

		public void SetDouble (string name, double value)
		{
			SetInternal (name, ConfigurationObjectType.Double, value);
		}

		public void SetString (string name, string value)
		{
			if (value == null)
				SetInternal (name, ConfigurationObjectType.Null, null);
			else
				SetInternal (name, ConfigurationObjectType.String, value);
		}

		public void SetChar (string name, char value)
		{
			SetInternal (name, ConfigurationObjectType.Char, value);
		}

		public void SetDecimal (string name, decimal value)
		{
			SetInternal (name, ConfigurationObjectType.Decimal, value);
		}

		public void SetDateTime (string name, DateTime value)
		{
			SetInternal (name, ConfigurationObjectType.DateTime, value.Ticks);
		}

		public void SetArray<T> (string name, T[] value)
		{
			if (value == null) {
				SetInternal (name, ConfigurationObjectType.Null, null);
			} else {
				Type type = typeof (T);
				if (!IsSerializable (type))
					throw new NotSupportedException ("The object must be Serializable in order to be stored in the configuration.");
				else
					SetInternal (name, ConfigurationObjectType.Array, value);
			}
		}

		public void SetList<T> (string name, IList<T> value)
		{
			if (value == null) {
				SetInternal (name, ConfigurationObjectType.Null, null);
			} else {
				if (!IsSerializable (value.GetType ()) || !IsSerializable (typeof (T)))
					throw new NotSupportedException ("Both the object and the generic type must be Serializable in order to be stored in the configuration.");
				else
					SetInternal (name, ConfigurationObjectType.List, value);
			}
		}

		protected internal abstract void SetInternal (string name, ConfigurationObjectType type, object value);

		protected internal abstract object GetInternal (string name, ConfigurationObjectType type);

		protected static bool IsSerializable (Type type)
		{
			object[] attribs = type.GetCustomAttributes (typeof (SerializableAttribute), true);
			return attribs.Length > 0;
		}

		// Internal type-safe check if an object with a given name exists.
		// <returns>0 if an object with the given name and type exists, -1 if the name does not exist, -2 if the name exists but with a different type.</returns>
		protected internal abstract int CheckType (string name, ConfigurationObjectType type);
	}
}